﻿using System.Windows.Media;
using System.Windows.Media.Imaging;
using Balloonerds.Algebra;

namespace RayTracer
{
    public class Sensor
    {
        public int HorizontalResolution { get; private set; }
        public int VerticalResolution { get; private set; }

        public double Width { get; private set; }
        public double Height { get; private set; }

        public double pixelSize;
        public double m0, n0;

        public Sensor(double width, int horizontalResolution, int verticalResolution)
        {
            HorizontalResolution = horizontalResolution;
            VerticalResolution = verticalResolution;

            Width = width;
            Height = width * verticalResolution / horizontalResolution;

            pixelSize = Width / HorizontalResolution;

            n0 = horizontalResolution / 2;
            m0 = verticalResolution / 2;
        }

        public WriteableBitmap GetBitmap()
        {
            return new WriteableBitmap(
                HorizontalResolution, VerticalResolution,
                96, 96,
                PixelFormats.Bgr32,
                null);
        }

        public static Sensor Film35mm320x200 { get { return new Sensor(.035, 320, 200); } }
        public static Sensor Film35mm640x480 { get { return new Sensor(.035, 640, 480); } }
    }

    public class Camera
    {
        public static Vector DefaultUp = new Vector(0, 1, 0);

        public Sensor Sensor { get; set; }

        private Vector up;
        private Vector position;
        private Vector lookTowards;
        private double f;

        private Vector focalPoint;

        // sensor local axis directions: x, y, z
        // from point of view: right, up, forward
        private Vector u, v, w; 


        public Vector Up
        {
            get { return up; }
            set
            {
                up = value;
                Recompute();
            }
        }
        public Vector Position
        {
            get { return position; }
            set
            {
                position = value;
                Recompute();
            }
        }
        public Vector LookTowards
        {
            get { return lookTowards; }
            set
            {
                lookTowards = value;
                Recompute();
            }
        }
        public double FocalLength
        {
            get { return f; }
            set
            {
                f = value;
                Recompute();
            }
        }


        public Camera(Vector position, Vector lookTowards, Vector up, double f, Sensor sensor)
        {
            this.position = position;
            this.lookTowards = lookTowards;
            this.up = up;
            this.f = f;

            Sensor = sensor;

            Recompute();
        }

        public Camera(Vector position, Vector lookTowards) :
            this(position, lookTowards, DefaultUp, .050, Sensor.Film35mm320x200)
        { }
        
        public Ray Cast(int x, int y)
        {
            var cu = ((2 * x + 1) * Sensor.pixelSize - Sensor.Width) / 2;
            var cv = ((2 * y + 1) * Sensor.pixelSize - Sensor.Height) / 2;

            var direction = (focalPoint + cu * u + cv * v - position).Normalized;

            return new Ray()
            {
                Origin = position,
                Direction = direction
            };
        }

        private void Recompute()
        {
            w = (lookTowards - position).Normalized;
            u = (up ^ w).Normalized;
            v = u ^ w;

            focalPoint = position + f * w;
        }
    }
}
